package roborally.model;

import static org.junit.Assert.*;

import org.junit.BeforeClass;
import org.junit.Test;

public class RobotTest {
	
	private static Robot robot, robot_2_2_DOWN_10000, robot_2_2_UP_10000, robot_2_2_LEFT_10000, 
	robot_2_2_RIGHT_10000, robot_full;
	
	@BeforeClass
	public static void setUpBeforeClass() throws Exception{
		robot_2_2_DOWN_10000 = new Robot(2,2,Orientation.DOWN,10000); 
		robot_2_2_UP_10000 = new Robot(2,2,Orientation.UP,10000);
		robot_2_2_LEFT_10000 = new Robot(2,2,Orientation.LEFT,10000);
		robot_2_2_RIGHT_10000 = new Robot(2,2,Orientation.RIGHT,10000);
		robot_full = new Robot(2,2,Orientation.RIGHT,20000);
	}
	

	@Test
	public final void defaultConstructor_SingleCase() {
		robot = new Robot();
		assertEquals(0L, robot.getPosition().getX());  //0L because position is a long
		assertEquals(0L, robot.getPosition().getY());
		assertEquals(20000, robot.getEnergy(), 0);
		assertEquals(20000, robot.getMaxEnergy(), 0);
		assertEquals(2,robot.getIntOrientation());
	}
	
	@Test
	public final void normalConstructor_LegalCase() throws Exception {
		assertEquals(2L, robot_2_2_DOWN_10000.getPosition().getX());
		assertEquals(2L, robot_2_2_DOWN_10000.getPosition().getY());
		assertEquals(10000, robot_2_2_DOWN_10000.getEnergy(), 0);
		assertEquals(20000, robot_2_2_DOWN_10000.getMaxEnergy(), 0);
		assertEquals(2,robot_2_2_DOWN_10000.getIntOrientation());
	}
	
	@Test(expected = IllegalArgumentException.class) // exception from the position
	public final void normalConstructor_IllegalCase() throws Exception {
		new Robot(-1,2,Orientation.LEFT,100);
	}
	
	@Test
	public final void extendedConstructor_LegalCase() throws Exception {
		Robot robot =  new Robot(2,2,Orientation.LEFT,5000,10000);
		assertEquals(2L,robot.getPosition().getX());
		assertEquals(2L,robot.getPosition().getY());
		assertEquals(5000,robot.getEnergy(),0);
		assertEquals(10000,robot.getMaxEnergy(),0);
		assertEquals(3,robot.getIntOrientation());	
	}
	
	@Test(expected = IllegalArgumentException.class)
	public final void extendedConstructor_IllegalCase() throws Exception {
		new Robot(2,-8,Orientation.LEFT,5000,10000);
	}
	
	@Test
	public final void isValidPosition_TrueCase() {
		assertTrue(Robot.isValidPosition(Board.dimension - 1, Board.dimension - 1));
	}
	
	@Test
	public final void isValidPosition_FalseCase() {
		assertFalse(Robot.isValidPosition(-10, -12));
	}
	
	@Test
	public final void isValidOrientation_TrueCase(){
		assertTrue(Robot.isValidOrientation(Orientation.RIGHT));
	}
	
	@Test
	public final void isValidOrientation_FalseCase(){
		assertFalse(Robot.isValidOrientation(null));
	}
	
	@Test
	public final void getEnergyFraction_SingleCase(){
		assertEquals(1,robot_full.getEnergyFraction(),0); //3de argument in assertEquals is epsilon (error margin)
	}
	
	@Test
	public final void recharge_SingleCase(){
		robot = new Robot(2,2,Orientation.LEFT,10000);
		robot.recharge(1000);
		assertEquals(11000,robot.getEnergy(),0);
	}
	
	@Test
	public final void isValidEnergy_TrueCase(){
		assertTrue(Robot.isValidEnergy(10000, 20000));
	}
	
	@Test
	public final void isValidEnergy_Overload(){
		assertFalse(Robot.isValidEnergy(210000, 10000));
		
	}
	
	@Test
	public final void isValidEnergy_Negative(){
		assertFalse(Robot.isValidEnergy(-10, 20000));
	}
	
	@Test
	public final void copyOf_SingleCase(){
		Robot copy = Robot.copyOf(robot_2_2_UP_10000);
		assertNotNull(copy);
		assertNotSame(robot_2_2_UP_10000, copy);
		assertEquals(robot_2_2_UP_10000.getPosition().getX(), copy.getPosition().getX());
		assertEquals(robot_2_2_UP_10000.getPosition().getY(), copy.getPosition().getY());
		assertEquals(robot_2_2_UP_10000.getOrientation(), copy.getOrientation());
		assertEquals(robot_2_2_UP_10000.getEnergy(), copy.getEnergy(),0);
		assertEquals(robot_2_2_UP_10000.getMaxEnergy(), copy.getMaxEnergy(),0);
		
	}
	
	//move and turn tests
	@Test
	public final void turnClockWise_SingleCase(){
		Robot copy = Robot.copyOf(robot_2_2_UP_10000);
		double en = copy.getEnergy();
		copy.turn(true);
		assertEquals(copy.getOrientation(), robot_2_2_RIGHT_10000.getOrientation());
		assertEquals(copy.getEnergy(), en - 100, 0);
	}
	
	@Test
	public final void turnCounterClockWise_SingleCase(){
		Robot copy = Robot.copyOf(robot_2_2_LEFT_10000);
		double en = copy.getEnergy();
		copy.turn(false);
		assertEquals(copy.getOrientation(), robot_2_2_DOWN_10000.getOrientation());
		assertEquals(copy.getEnergy(), en - 100, 0);
		
	}
	
	@Test
	public final void move_SingleCase(){
		Robot copy = Robot.copyOf(robot_2_2_LEFT_10000);
		double en = copy.getEnergy();
		copy.move();
		assertEquals(copy.getPosition().getX(), 1);
		assertEquals(copy.getPosition().getY(), 2);
		assertEquals(copy.getEnergy(), en - 500, 0);
	}
	
	@Test
	public final void moveOneFieldCloserTo_SamePos(){
		Robot robot = new Robot(2,2,Orientation.UP,10000);
		robot.moveOneFieldCloserTo(2, 2);
		assertEquals(2, robot.getPosition().getX());
		assertEquals(2, robot.getPosition().getY());
		assertEquals(10000, robot.getEnergy(), 0);
	}
	
	//getEnergyRequiredToReach tests
	@Test
	public final void getEnergyRequiredToReach_Case_SamePos(){
		Robot robot_2_2 = new Robot(2,2,Orientation.DOWN,10000);
		assertEquals(0, robot_2_2.getEnergyRequiredToReach(2, 2), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_Case_1DUp_Up(){
		assertEquals(Robot.ECOST_MOVE, robot_2_2_UP_10000.getEnergyRequiredToReach(2, 1), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_Case_1DRight_Up(){
		assertEquals(Robot.ECOST_TURN + Robot.ECOST_MOVE, robot_2_2_UP_10000.getEnergyRequiredToReach(3, 2), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_Case_1DDown_Up(){
		assertEquals(2 * Robot.ECOST_TURN + Robot.ECOST_MOVE, robot_2_2_UP_10000.getEnergyRequiredToReach(2, 3), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_Case1DLeft_Up(){
		assertEquals(Robot.ECOST_TURN + Robot.ECOST_MOVE, robot_2_2_UP_10000.getEnergyRequiredToReach(1, 2), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_Case_LBtoRT_Up(){
		assertEquals(2 * Robot.ECOST_MOVE + Robot.ECOST_TURN, robot_2_2_UP_10000.getEnergyRequiredToReach(3, 1), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_Case_LTtoRB_Up(){
		assertEquals(2 * Robot.ECOST_TURN + 2 * Robot.ECOST_MOVE, robot_2_2_UP_10000.getEnergyRequiredToReach(3, 3), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_Case_RTtoLB_Up(){
		assertEquals(2 * Robot.ECOST_TURN + 2 * Robot.ECOST_MOVE, robot_2_2_UP_10000.getEnergyRequiredToReach(1, 3), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_Case_RBtoLT_Up(){
		assertEquals(2 * Robot.ECOST_MOVE + Robot.ECOST_TURN, robot_2_2_UP_10000.getEnergyRequiredToReach(1, 1), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_Case_LTtoRB_Left(){
		assertEquals(2 * Robot.ECOST_TURN + 2 * Robot.ECOST_MOVE, robot_2_2_LEFT_10000.getEnergyRequiredToReach(3, 3), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_Case_LBtoRT_Down(){
		assertEquals(2 * Robot.ECOST_TURN + 2 * Robot.ECOST_MOVE, robot_2_2_DOWN_10000.getEnergyRequiredToReach(3, 1), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_Case_LBtoRT_Left(){
		assertEquals(2 * Robot.ECOST_TURN + 2 * Robot.ECOST_MOVE, robot_2_2_LEFT_10000.getEnergyRequiredToReach(3, 1), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_1DRight_Left(){
		assertEquals(2 * Robot.ECOST_TURN + Robot.ECOST_MOVE, robot_2_2_LEFT_10000.getEnergyRequiredToReach(3, 2), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_RTtoLB_Right(){
		assertEquals(2 * Robot.ECOST_TURN + 2 * Robot.ECOST_MOVE, robot_2_2_RIGHT_10000.getEnergyRequiredToReach(1, 3), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_RBtoLT_Right(){
		assertEquals(2 * Robot.ECOST_TURN + 2 * Robot.ECOST_MOVE, robot_2_2_RIGHT_10000.getEnergyRequiredToReach(1, 1), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_RBtoLT_Down(){
		assertEquals(2 * Robot.ECOST_TURN + 2 * Robot.ECOST_MOVE, robot_2_2_DOWN_10000.getEnergyRequiredToReach(1, 1), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_1DLeft_Right(){
		assertEquals(2 * Robot.ECOST_TURN + Robot.ECOST_MOVE, robot_2_2_RIGHT_10000.getEnergyRequiredToReach(1, 2), 0);
	}
	
	@Test
	public final void getEnergyRequiredToReach_1DUp_Down(){
		assertEquals(2 * Robot.ECOST_TURN + Robot.ECOST_MOVE, robot_2_2_DOWN_10000.getEnergyRequiredToReach(2,1), 0);
	}
	

	
	//isNextTo tests
	@Test
	public final void isNextTo_CaseTrueDx(){
		Robot r1 = new Robot(2,2,Orientation.RIGHT,10000);
		Robot r2 = new Robot(3,2,Orientation.LEFT,10000);
		assertTrue(Robot.isNextTo(r1,r2));
		r2.setPosition(1,2);
		assertTrue(Robot.isNextTo(r1, r2));
		
	}
	
	@Test
	public final void isNextTo_CaseTrueDy(){
		Robot r1 = new Robot(2,2,Orientation.RIGHT,10000);
		Robot r2 = new Robot(2,3,Orientation.LEFT,10000);
		assertTrue(Robot.isNextTo(r1,r2));
		r2.setPosition(2,1);
		assertTrue(Robot.isNextTo(r1, r2));
	}
	
	@Test
	public final void isNextTo_CaseFalseDx(){
		Robot r1 = new Robot(2,2,Orientation.RIGHT,10000);
		Robot r2 = new Robot(4,2,Orientation.LEFT,10000);
		assertFalse(Robot.isNextTo(r1,r2));
		r2.setPosition(0,2);
		assertFalse(Robot.isNextTo(r1, r2));
	}
	
	@Test
	public final void isNextTo_CaseFalseDy(){
		Robot r1 = new Robot(2,2,Orientation.RIGHT,10000);
		Robot r2 = new Robot(2,4,Orientation.LEFT,10000);
		assertFalse(Robot.isNextTo(r1,r2));
		r2.setPosition(2,0);
		assertFalse(Robot.isNextTo(r1, r2));
	}
	
	
	//moveNextTo Tests
	@Test
	public final void moveNextTo_sufficientEnergy_LT_RB(){
		//r1: LT, r2: RB, dx = 3, dy = 2
		Robot r1 = new Robot(2,2,Orientation.RIGHT,10000);
		Robot r2 = new Robot(5,4,Orientation.LEFT,10000);
		Robot.moveNextTo(r1, r2);
		//test if next to each other
		assertTrue(Robot.isNextTo(r1, r2));
		//test energy consumed
		assertEquals(2*Robot.ECOST_MOVE,10000-r1.getEnergy(),0);
		assertEquals(2*Robot.ECOST_MOVE+Robot.ECOST_TURN,10000-r2.getEnergy(),0);
		//test the robots positions
		assertTrue( r1.getPosition().isEqualTo(new Position(4,2)) );
		assertTrue( r2.getPosition().isEqualTo(new Position(4,3)) );
	}
	@Test
	public final void moveNextTo_sufficientEnergy_LB_RT(){
		//r1:LB, r2:RT, dx = 3, dy = -3
		Robot r1 = new Robot(1,3,Orientation.RIGHT,10000);
		Robot r2 = new Robot(4,0,Orientation.LEFT,10000);	
		Robot.moveNextTo(r1, r2);
		//test if next to each other
		assertTrue(Robot.isNextTo(r1, r2));
		//test energy consumed
		assertEquals(3*Robot.ECOST_MOVE+Robot.ECOST_TURN,10000-r1.getEnergy(),0);
		assertEquals(2*Robot.ECOST_MOVE+Robot.ECOST_TURN,10000-r2.getEnergy(),0);	
		//test the robots positions
		assertTrue( r1.getPosition().isEqualTo(new Position(3,2)) );
		assertTrue( r2.getPosition().isEqualTo(new Position(3,1)) );
	}
	
	@Test
	public final void moveNextTo_insufficientEnergy(){
		Robot r1 = new Robot(1,3,Orientation.RIGHT,600);
		Robot r2 = new Robot(4,0,Orientation.UP,1200);
		Robot.moveNextTo(r1, r2);
		//test if next to each other
		assertFalse(Robot.isNextTo(r1, r2));
		//test robots positions
		assertTrue( r1.getPosition().isEqualTo(new Position(2,3)) );
		assertTrue( r2.getPosition().isEqualTo(new Position(2,0)) );
		//test robots orientations
		assertTrue( r1.getOrientation().equals(Orientation.UP));
		assertTrue( r2.getOrientation().equals(Orientation.LEFT));
		//test robots energy-levels
		assertEquals( 600 - (Robot.ECOST_MOVE + Robot.ECOST_TURN), r1.getEnergy(),0);
		assertEquals( 1200 - (2*Robot.ECOST_MOVE + Robot.ECOST_TURN), r2.getEnergy(),0);
	}
	
	@Test
	public final void moveNextTo_samePos_Case_NoExc(){
		//normal case, we can move the first robot a field up in its direction, no exception
		Robot r1 = new Robot(2,0,Orientation.DOWN,10000);
		Robot r2 = new Robot(2,0,Orientation.DOWN,10000);	
		Robot.moveNextTo(r1, r2);
		//test if next to each other
		assertTrue(Robot.isNextTo(r1, r2));
		//test robot1's new position
		assertTrue( r1.getPosition().isEqualTo(new Position(2,1)) );
		assertTrue( r2.getPosition().isEqualTo(new Position(2,0)) );
	}
	@Test
	public final void moveNextTo_samePos_Case_Exc(){
		//in this case an exception is thrown when we try to move the first robot in its direction, we catch it and move the second robot successfully instead
		Robot r1 = new Robot(2,0,Orientation.UP,10000);
		Robot r2 = new Robot(2,0,Orientation.DOWN,10000);	
		Robot.moveNextTo(r1, r2);
		//test if next to each other
		assertTrue(Robot.isNextTo(r1, r2));
		//test robot1's new position
		assertTrue( r1.getPosition().isEqualTo(new Position(2,0)) );
		assertTrue( r2.getPosition().isEqualTo(new Position(2,1)) );
	}
	@Test
	public final void moveNextTo_samePos_Case_DoubleExc(){
		//in this case also the second robot will throw an exception when invoke move(), thus we turn the first robot and then successfully move it.
		Robot r1 = new Robot(2,0,Orientation.UP,10000);
		Robot r2 = new Robot(2,0,Orientation.UP,10000);	
		Robot.moveNextTo(r1, r2);
		//test if next to each other
		assertTrue(Robot.isNextTo(r1, r2));
		//test robot1's new position
		assertTrue( r1.getPosition().isEqualTo(new Position(3,0)) );
		assertTrue( r2.getPosition().isEqualTo(new Position(2,0)) );
	}
	@Test
	public final void moveNextTo_samePos_Case_LT(){
		//this case is an exception on Case3, because in this case turning the robot clockwise and moving will still result in an exception being thrown, 
		//in this special case we need to turn the robot counterclockwise.
		Robot r1 = new Robot(0,0,Orientation.LEFT,10000);
		Robot r2 = new Robot(0,0,Orientation.LEFT,10000);	
		Robot.moveNextTo(r1, r2);
		//test if next to each other
		assertTrue(Robot.isNextTo(r1, r2));
		//test robot1's new position
		assertTrue( r1.getPosition().isEqualTo(new Position(0,1)) );
		assertTrue( r2.getPosition().isEqualTo(new Position(0,0)) );
	}
	@Test
	public final void moveNextTo_samePos_Case_RT(){
		//special case for when the robot is on the right top field of the board.
		Robot r1 = new Robot(Board.dimension-1,0,Orientation.UP,10000);
		Robot r2 = new Robot(Board.dimension-1,0,Orientation.UP,10000);	
		Robot.moveNextTo(r1, r2);
		//test if next to each other
		assertTrue(Robot.isNextTo(r1, r2));
		//test robot1's new position
		assertTrue( r1.getPosition().isEqualTo(new Position(Board.dimension-2,0)) );
		assertTrue( r2.getPosition().isEqualTo(new Position(Board.dimension-1,0)) );
	}
	@Test
	public final void moveNextTo_samePos_Case_RB(){
		//special case for when the robot is on the right bottom field of the board.
		Robot r1 = new Robot(Board.dimension-1,Board.dimension-1,Orientation.RIGHT,10000);
		Robot r2 = new Robot(Board.dimension-1,Board.dimension-1,Orientation.RIGHT,10000);	
		Robot.moveNextTo(r1, r2);
		//test if next to each other
		assertTrue(Robot.isNextTo(r1, r2));
		//test robot1's new position
		assertTrue( r1.getPosition().isEqualTo(new Position(Board.dimension-1,Board.dimension-2)) );
		assertTrue( r2.getPosition().isEqualTo(new Position(Board.dimension-1,Board.dimension-1)) );
	}
	@Test
	public final void moveNextTo_samePos_Case_LB(){
		//special case for when the robot is on the left bottom field of the board.
		Robot r1 = new Robot(0,Board.dimension-1,Orientation.DOWN,10000);
		Robot r2 = new Robot(0,Board.dimension-1,Orientation.DOWN,10000);	
		Robot.moveNextTo(r1, r2);
		//test if next to each other
		assertTrue(Robot.isNextTo(r1, r2));
		//test robot1's new position
		assertTrue( r1.getPosition().isEqualTo(new Position(1,Board.dimension-1)) );
		assertTrue( r2.getPosition().isEqualTo(new Position(0,Board.dimension-1)) );
	}
	
	
	
}
